package com.hwtx.form.domain.handle.datamodel;

import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.listener.DataModelChangeEvent;
import com.hwtx.form.domain.listener.EventPublisher;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.vo.ResultVo;
import com.hwtx.form.persistence.DataSourceRepo;
import com.hwtx.form.persistence.SQLType;
import com.hwtx.form.persistence.ds.metadata.Column;
import com.hwtx.form.persistence.ds.metadata.Constraint;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.DEFAULT_FIELDS;
import static com.hwtx.form.domain.FormConstants.FIELD_POSITION_FIRST;
import static com.hwtx.form.domain.handle.datamodel.DataModel.buildDataModelDetailByColumn;

@Slf4j
@Service
public class DbSynService {

    @Resource
    EventPublisher eventPublisher;
    @Resource
    private DataSourceRepo dataSourceRepo;
    @Resource
    private DataModelRepo dataModelRepo;

    public ResultVo sync(String modelName, boolean del) {
        try {
            List<DataModelDetail> details = dataModelRepo.getDetails(modelName, FormConstants.Status.VALID);
            DataModel dataModel = dataModelRepo.getDataModel(modelName);
            String msg = null;
            if (dataModel == null || dataModel.getMaster() == null) {
                return ResultVo.builder().msg("请先保存模型").success(false).build();
            }
            String master = dataModel.getMaster();
            DataSourceContextHolder.checkDataSourceKey(dataModel.getDs());
            Map<String, Column> columns = dataSourceRepo.getColumns(master);
            if (dataSourceRepo.exist(master)) {
                if (emptyOrOnlyDefaultFields(details)) {
                    Map<String, DataModelDetail> tables = details.stream().collect(Collectors.toMap(DataModelDetail::getName, Function.identity()));
                    AtomicInteger num = new AtomicInteger(1);
                    msg = synDataModelDetail(columns, master, modelName, num, column -> !tables.containsKey(column));
                } else {
                    msg = synDbAndConfigEach(columns, master, details, del);
                }
            } else {
                if (!details.isEmpty()) {
                    msg = dataSourceRepo.create(master, details);
                }
            }
            eventPublisher.publishEvent(new DataModelChangeEvent(this, modelName));
            return ResultVo.builder().success(true).msg(msg).build();
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
    }

    private boolean emptyOrOnlyDefaultFields(List<DataModelDetail> details) {
        if (CollectionUtils.isEmpty(details)) {
            return true;
        }
        return details.stream().allMatch(formConfigDb -> DEFAULT_FIELDS.containsKey(formConfigDb.getName()));
    }

    private String synDataModelDetail(Map<String, Column> columns, String table, String modelName, AtomicInteger num, Predicate<String> filter) {
        List<String> uniqueColumns = dataSourceRepo.getConstraintColumns(table);
        columns.entrySet().stream().filter(entry -> filter.test(entry.getKey())).forEach(entry -> dataModelRepo.saveDetail(buildDataModelDetailByColumn(table, entry.getKey(), entry.getValue(), num, modelName, uniqueColumns)));
        return "同步数据库字段到表单配置成功";
    }

    private String synDbAndConfigEach(Map<String, Column> existDbColumns, String tableName,
                                      List<DataModelDetail> details, boolean forceDel) {
        StringBuilder sb = new StringBuilder();
        Set<DataModelDetail> addCollections = details.stream().filter(col -> !existDbColumns.containsKey(col.getName())).collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(addCollections)) {
            List<String> columns = existDbColumns.values().stream().map(Column::getName).toList();
            for (DataModelDetail dataModelDetail : addCollections) {
                dataModelDetail.setAfterFieldName(FIELD_POSITION_FIRST);
                if (dataModelDetail.getNum() != 1) {
                    dataModelDetail.setAfterFieldName(columns.getLast());
                }
                dataSourceRepo.addColumn(tableName, dataModelDetail);
            }
            sb.append("数据库新增字段：").append(addCollections.stream().map(DataModelDetail::getName).collect(Collectors.joining(","))).append("\n");
        }
        Map<String, DataModelDetail> groupByCols = details.stream().collect(Collectors.toMap(DataModelDetail::getName, Function.identity()));
        Set<String> removedCollections = new HashSet<>();
        Set<DataModelDetail> updateCollections = new HashSet<>();
        existDbColumns.forEach((colName, column) -> {
            if (!groupByCols.containsKey(colName)) {
                removedCollections.add(colName);
            } else {
                boolean isSame = isSame(groupByCols.get(colName), column);
                if (!isSame) {
                    updateCollections.add(groupByCols.get(colName));
                }
            }
        });
        if (CollectionUtils.isNotEmpty(removedCollections)) {
            if (forceDel) {
                dataSourceRepo.removeColumns(tableName, removedCollections, true);
                sb.append("数据库删除字段：").append(String.join(",", removedCollections)).append("\n");
            } else {
                sb.append("数据库字段：").append(String.join(",", removedCollections)).append("未选择强制删除，无法生效").append("\n");
            }
        }
        if (CollectionUtils.isNotEmpty(updateCollections)) {
            dataSourceRepo.updateColumns(tableName, updateCollections);
            sb.append("数据库更新字段：").append(updateCollections.stream().map(DataModelDetail::getName).collect(Collectors.joining(",")));
        }
        List<String> uniques = details.stream().filter(col ->
                        Objects.equals(col.getIsUnique(), (byte) 1) && !Objects.equals(col.getPriKey(), (byte) 1))
                .map(DataModelDetail::getName).toList();
        List<String> existUniques = dataSourceRepo.getConstraintColumns(tableName);
        if (CollectionUtils.isEmpty(existUniques) && CollectionUtils.isNotEmpty(uniques)) {
            addUnique(uniques, tableName, sb);
        } else if (CollectionUtils.isNotEmpty(existUniques) && CollectionUtils.isEmpty(uniques)) {
            dropUnique(existUniques, tableName, sb);
        } else if (CollectionUtils.isNotEmpty(existUniques) && CollectionUtils.isNotEmpty(uniques)) {
            Collection<String> addUniques = CollectionUtils.subtract(uniques, existUniques);
            if (CollectionUtils.isNotEmpty(addUniques)) {
                addUnique(addUniques, tableName, sb);
            }
            Collection<String> removeUniques = CollectionUtils.subtract(existUniques, uniques);
            if (CollectionUtils.isNotEmpty(removeUniques)) {
                dropUnique(removeUniques, tableName, sb);
            }
        }
        if (sb.isEmpty()) {
            sb.append("数据已同步，无需再次操作");
        }
        return sb.toString();
    }

    private void addUnique(Collection<String> addUniques, String tableName, StringBuilder sb) {
        sb.append("数据库新增唯一约束：");
        addUniques.forEach(c -> {
            Constraint constraint = new Constraint();
            constraint.setTable(tableName);
            constraint.addColumn(c);
            constraint.setName("unique_" + c);
            constraint.setType(Constraint.TYPE.UNIQUE);
            dataSourceRepo.add(constraint);
            sb.append(c).append(",");
        });
        sb.deleteCharAt(sb.length() - 1);
    }

    private void dropUnique(Collection<String> removeUniques, String tableName, StringBuilder sb) {
        sb.append("数据库删除唯一约束：");
        removeUniques.forEach(c -> {
            Constraint constraint = new Constraint();
            constraint.setTable(tableName);
            constraint.addColumn(c);
            dataSourceRepo.drop(constraint);
            sb.append(c).append(",");
        });
    }

    private boolean isSame(DataModelDetail dataModelDetail, Column column) {
        if (!Objects.equals(dataModelDetail.getType(), SQLType.getType(column.getTypeName()))) {
            return false;
        }
        if (!SQLType.getNoCaseOfLengthAndPrecision().contains(dataModelDetail.getType()) &&
                dataModelDetail.getLength() != null && !Objects.equals(dataModelDetail.getLength(), column.getPrecision())) {
            return false;
        }
        if (!SQLType.getNoCaseOfLengthAndPrecision().contains(dataModelDetail.getType())
                && dataModelDetail.getPoints() != null && !Objects.equals(dataModelDetail.getPoints(), column.getScale())) {
            return false;
        }
        if (dataModelDetail.getNotNull() != null) {
            if (dataModelDetail.getNotNull() != (byte) (column.isNullable() == 1 ? 0 : 1)) {
                return false;
            }
        }
        if (dataModelDetail.getPriKey() != null) {
            if (dataModelDetail.getPriKey() != (byte) (column.isPrimaryKey() == 1 ? 1 : 0)) {
                return false;
            }
        }
        if (!Objects.equals(dataModelDetail.getDefValue(), column.getDefaultValue())) {
            return false;
        }
        return Objects.equals(dataModelDetail.getDescription(), column.getComment());
    }
}
